package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    //对于 tcp 服务器端 首先应该创建 ServerSocket 对象
    //这个套接字可以监听来自客户端的连接请求。
    //一旦客户端请求连接，服务器可以接受这个连接并创建一个新的套接字，用于与客户端进行通信。
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int prot) throws IOException {
        this.serverSocket = new ServerSocket(prot);
    }

    //能一直扩容
    //如果固定线程个数的线程池，不太合适，就限制了最多有多少个客户端同时连接
    ExecutorService pool = Executors.newCachedThreadPool();

    public void start() throws IOException {
        System.out.println("服务器启动！！！");
        while (true){
            //1.服务器一上来要处理 客户端发来的来连接
            //通过 accept 来接受或者监听 客户端发来的连接
            //返回的结果就是 socket 对象
            /*
            服务器一旦启动，就会立即执行到这里来
            如果客户端没有来连接过来，accept 也会产生阻塞
            直到 有客户端 真的连接上来
             */
            Socket clientSocket = serverSocket.accept();
            // clientSocket 使用完应该关闭

            //为了处理后续的请求响应  单独写一个方法
            //连接操作的方法 ：
            //多线程
            Thread t = new Thread(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();

            //接下来是创建线程池
            //把任务添加到线程池中
//            pool.submit(new Runnable() {
//                @Override
//                public void run() {
//                    try {
//                        processConnection(clientSocket);
//                    } catch (IOException e) {
//                        throw new RuntimeException(e);
//                    }
//                }
//            });
        }
    }

    //通过这个方法来处理一个连接
    private void processConnection(Socket clientSocket) throws IOException {
        //1.打印一个日志，告知说当前有客户端连上了
        System.out.printf("[%s:%d] 客户端上线！\n",clientSocket.getInetAddress(),clientSocket.getPort());
        //2.从 socket 中获取流对象，来进一步的进行后续操作
        //通过 try 里面创建的两个实例来获取 clientSocket 中的流对象
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()){
            //3.弄一个循环，针对每个连接，客户端可能发来多个请求，服务器也就需要返回多个响应
            //  利用 inputStream.read() 可以读取 读出来是字节 还需要转换成字符串
            //  但是利用 scanner 更方便  读出来就是 String
            Scanner scanner = new Scanner(inputStream);
            //可以使用 scanner 对象来从 inputStream 中读取数据，而不必直接操作底层的输入流方法。
            // 例如，你可以使用 scanner.nextInt()、scanner.nextLine() 等方法来从输入流中读取整数或者一行文本。
            //
            while (true){
                //3.1  读取请求并解析
                //scanner.next();读到 “空白符” 才会读取完毕
                //  空白符 是一类字符的统称 包括不限于 换行、回车、空格、制表符、翻页符........
                //呢么就要要求在客户端 发送数据的时候，务必在每个请求的末尾 填上空白符 例如填上 \n
                // 上面的要求相当于是 程序员 对通信细节的约定

                if (!scanner.hasNext()){
                    //如果发现后续没有数据，说明连接已经断开了
                    System.out.printf("[%s:%d] 客户端下线！\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                String request = scanner.next();

                //3.2 根据请求计算响应
                // 写一个方法
                String response = process(request);

                //3.3 把响应写回到客户端
                outputStream.write(response.getBytes(),0,response.getBytes().length);
                outputStream.flush();

                //3.4 服务器打印日志
                System.out.printf("[%s:%d] request = %s, responce = %s\n",
                        clientSocket.getInetAddress(),
                        clientSocket.getPort(),
                        request,
                        response);

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            clientSocket.close();
        }

    }

    //根据请求计算响应 方法
    // 由于是回显服务器 直接把 请求 作为响应返回回去
    public String process(String request) {
        //给响应的后面加上一个换行符，使客户端读取到响应的时候，也有明确的分割符
        return request + '\n';
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}
